/*	$Id: swtab.c 5 2008-07-29 02:22:57Z phrakt $	*/
/*
 * Copyright (c) 2005 Jean-Francois Brousseau <jfb@openbsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "swtab.h"
#include "pforge.h"
#include "private.h"


struct pf_swtab_ent {
	void         *se_data;
};


struct pf_swtab {
	int                  st_flags;
	int                  st_size;
	int                  st_max;
	struct pf_swtab_ent *st_tab;

	void               (*st_free)(void *);
#ifdef PF_THREAD_SAFE
	pthread_mutex_t    st_lock;
#endif
};



/*
 * pf_swtab_alloc()
 *
 * Allocate a switch table which has space for <size> entries and can be
 * grown to accomodate for up to <max> entries.
 * Returns a pointer to the new switch table on success, or NULL on failure.
 */
struct pf_swtab*
pf_swtab_alloc(int size, int max, int flags, void (*free_cb)(void *))
{
	int ret;
	size_t sz;
	struct pf_swtab *swtab;

	sz = size * sizeof(struct pf_swtab_ent);
	if (sz <= size) {
		pf_seterr(PF_ERR_OFLOW);
		return (NULL);
	}

	if ((swtab = (struct pf_swtab *)pf_malloc(sizeof(*swtab))) == NULL)
		return (NULL);
	memset(swtab, 0, sizeof(*swtab));

#ifdef PF_THREAD_SAFE
	if ((ret = pthread_mutex_init(&(swtab->st_lock), NULL)) != 0) {
		pf_free(swtab);
		return (NULL);
	}
#endif

	swtab->st_tab = (struct pf_swtab_ent *)pf_malloc(sz);
	if (swtab->st_tab == NULL) {
	}

	swtab->st_flags = flags;
	swtab->st_free = free_cb;

	return (swtab);
}


/*
 * pf_swtab_free()
 *
 * Free a switch table structure and all of its entries.
 */
void
pf_swtab_free(struct pf_swtab *swtab)
{
	int i;

	if (swtab == NULL)
		return;

	if (swtab->st_tab != NULL) {
		if (swtab->st_free != NULL) {
			for (i = 0; i < swtab->st_size; i++) {
				if (swtab->st_tab[i].se_data == NULL)
					continue;

				swtab->st_free(swtab->st_tab[i].se_data);
			}
		}

		pf_free(swtab->st_tab);
	}

	pf_free(swtab);
}

/*
 * pf_swtab_getslot()
 *
 * Get a free slot in the capture file table.  Once an empty slot is found,
 * its pointer is set to PFCAP_SLOT_USED so it won't be accidentally
 * reassigned by another request before the entry is actually set.
 * Returns the index of the entry within the switch table on success, or
 * -1 on failure.
 */
int
pf_swtab_insert(struct pf_swtab *swtab, int id, void *data)
{
	int ret = 0;
	size_t newsz;
	void *tmp;

	if (id < -1) {
		pf_seterr(PF_ERR_RANGE);
		return (-1);
	}

#ifdef PF_THREAD_SAFE
	if ((ret = pthread_mutex_lock(&(swtab->st_lock))) != 0) {
		return (-1);
	}
#endif

	if (id == -1) {
		/*
		 * Attempt to find a free slot in the table.  If no
		 * available slot is found, <id> will be equal to the table
		 * size and the next block will attempt to grow the table
		 * for a new empty slot.
		 */
		for (id = 0; id < swtab->st_size; id++)
			if (swtab->st_tab[id].se_data == NULL)
				break;
	}

	if (id >= swtab->st_size) {
		if (id >= swtab->st_max) {
			pf_seterr(PF_ERR_RANGE);
			ret = -1;
		} else {
			newsz = MAX((size_t)id, ((size_t)swtab->st_size + 8));

			if ((tmp = pf_realloc(swtab->st_tab,
			    newsz * sizeof(struct pf_swtab_ent))) == NULL) {
				ret = -1;
			} else {
				swtab->st_tab = (struct pf_swtab_ent *)tmp;
				swtab->st_size = newsz;

				/* NULLify the new entries */
				for (ret = swtab->st_size;
				    ret < (int)newsz; ret++)
					swtab->st_tab[ret].se_data = NULL;
			}

		}
	}

	if (ret != -1) {
		swtab->st_tab[id].se_data = data;
	}

#ifdef PF_THREAD_SAFE
	(void)pthread_mutex_unlock(&(swtab->st_lock));
#endif

	return (id);
}

/*
 * pf_swtab_remove()
 *
 * Remove the entry at index <id> from the switch table <swtab>.
 */
int
pf_swtab_remove(struct pf_swtab *swtab, int id)
{
	int ret;
	void *ent;

#ifdef PF_THREAD_SAFE
	if ((ret = pthread_mutex_lock(&(swtab->st_lock))) != 0) {
		return (-1);
	}
#endif

	if ((swtab->st_size <= id) ||
	    (swtab->st_tab[id].se_data == NULL)) {
		pf_seterr(PF_ERR_NOENT);
		ret = -1;
	} else {
		swtab->st_tab[id].se_data = NULL;
	}

#ifdef PF_THREAD_SAFE
	(void)pthread_mutex_unlock(&(swtab->st_lock));
#endif

	return (ret);
}

/*
 * pf_swtab_get()
 *
 * Retrieve the entry with index <id> in the switch table <swtab>.
 * Returns a pointer to the entry on success, or NULL on failure.
 */
void*
pf_swtab_get(struct pf_swtab *swtab, int id)
{
	int ret;
	void *ent;

	if (id < 0) {
		pf_seterr(PF_ERR_RANGE);
		return (NULL);
	}

#ifdef PF_THREAD_SAFE
	if ((ret = pthread_mutex_unlock(&(swtab->st_lock))) != 0) {
		return (NULL);
	}
#endif

	ent = swtab->st_tab[id].se_data;

#ifdef PF_THREAD_SAFE
	(void)pthread_mutex_unlock(&(swtab->st_lock));
#endif

	return (ent);
}
